﻿
Imports System.Threading
Imports lock_t = lock(Of locks.monitorlock)

'multi writer / multi reader model
'lock / unlock is for writer, which will also have a sequentialLock to protect.
'wait / release is for reader, suppose reader will not change, but only read, so no protection needed.
'readers will not block other readers, except for fl check & sl increment / decrement
'writers will block other writers, meanwhile any readers
Public Structure duallock
    Private fl As lock_t
    Private sl As Int32

    Shared Sub New()
        assert([default](Of Int32)() = 0)
    End Sub

    Public Sub lock()
        fl.wait()
        lazy_wait_until(Function(ByRef x) atomic.read(x) = 0,
                        sl)
    End Sub

    Public Sub unlock()
        assert(sl = 0)
        fl.release()
    End Sub

    Public Sub writerWait()
        lock()
    End Sub

    Public Sub writerRelease()
        unlock()
    End Sub

    Public Sub wait()
        fl.wait()
        Interlocked.Increment(sl)
        fl.release()
    End Sub

    Public Sub release()
        Interlocked.Decrement(sl)
    End Sub

    Public Sub readerWait()
        wait()
    End Sub

    Public Sub readerRelease()
        release()
    End Sub

    'suppose test_string will not changed during any reader threads
    Private Shared test_string As String = Nothing
    Private Shared l As duallock = Nothing
    Private Shared round As Int64 = 1024

    Private Shared Sub _sleep()
        sleep(rnd(0, 100, True))
    End Sub

    Private Shared Sub writer()
        Dim r As Int64 = 0
        r = round
        'keep the thread overall time almost the same
        While r > 0
            If rnd(0, 1) < 0.2 Then
                l.lock()
                _sleep()
                test_string = createGUID()
                raiseError("change test_string to " + test_string)
                _sleep()
                l.unlock()
            Else
                _sleep()
                _sleep()
            End If
            r -= 1
        End While
    End Sub

    Private Shared Sub reader()
        Dim r As Int64 = 0
        r = round
        While r > 0
            l.wait()
            Dim s As String = Nothing
            copy(s, test_string)
            raiseError("read test_string " + s)
            _sleep()
            assert(strsame(s, test_string))
            _sleep()
            assert(strsame(s, test_string))
            l.release()
            r -= 1
        End While
    End Sub

    Public Shared Sub unittest()
        Dim threadcount As Int64 = 0
        threadcount = 8
        If isreleasebuild() Then
            threadcount <<= 2
            round <<= 2
        End If
        l = New duallock()
        raiseError("********** duallock Automatical Unittest ***********", errorHandle.errorType.application)
        Dim threads((threadcount << 1) - 1) As Thread
        For i As Int64 = 0 To (threadcount >> 1) - 1
            threads(i) = startThread(AddressOf writer)
        Next
        For i As Int64 = (threadcount >> 1) To (threadcount << 1) - 1
            threads(i) = startThread(AddressOf reader)
        Next

        For i As Int64 = 0 To threads.Length() - 1
            assert(Not threads(i) Is Nothing)
            threads(i).Join()
        Next
        raiseError("******* finish duallock Automatical Unittest *******", errorHandle.errorType.application)
    End Sub
End Structure
